﻿/* Derived from CART.DLL, Cart Plugin for KSP.
Original Author: Tony Chernogovsky, SPb, Russia.
Previous Maintainer: Callum King-Underwood, England.
Current Maintainer: Andrea Cappelletti, Milan, Italy aka CleverWalrus.
License: CC-BY-SA, Attribution & Share-alike Creative Common Licence. 
Feel free to modify as long as:
a) original author is mentioned, and 
b) you distribute your code under the same license.*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

using CleverBobCat.Core;

namespace CleverBobCat.WheeledVehicle
{
    class ModuleCleverWheeledVehicle : PartModule
    {
        [KSPField]
        public bool debug = false;

        // Engine:
        // FrictionTorque is limiting cart's top speed and coasting distance.
        [KSPField]
        public float forwardTorque = 0.5F;
        [KSPField]
        public float backwardTorque = 0.3F;
        [KSPField]
        public float brakeTorque = 2.5F;
        [KSPField]
        public float frictionTorque = 0.005F;

        // Steering:
        // When speed is less than steerLimitSpeed then maxSteerAngle is
        // used. When speed is higher then steering is reverse-proportional
        // to speed (steerSpeedFalloff being the coefficient).
        // SteerVisisbility is the coefficient for wheel animation.
        [KSPField]
        public float maxSteerAngle = 15;
        [KSPField]
        public float steerLimitSpeed = 5;
        [KSPField]
        public float steerSpeedFalloff = 2;
        [KSPField]
        public float steerVisibility = 2;

        // Suspension:
        // If "deployable" is true then wheels can be deployed/retracted with
        // a "landing gear" key; "deployTime" being the process duration, in
        // seconds.
        [KSPField]
        public float wheelMass = 0.1F;
        [KSPField]
        public float suspensionSpring = 5;
        [KSPField]
        public float suspensionDamper = 0.1F;
        [KSPField]
        public bool deployable = false;
        [KSPField]
        public float deployTime = 0.5F;

        // Friction:
        // See WheelFrictionCurve.stiffness. 0 -- no friction (wheels always
        // slip).
        [KSPField]
        public float forwardFriction = 1F;
        [KSPField]
        public float sidewaysFriction = 1F;

        // Compensation:
        // compensationLowGee is the force dependant on vessel mass which is 
        // applied to cart when operating at Mun. 0 means no force, 1 makes 
        // the cart behave exactly as it does at Kerbin.
        // compensationSpeed determines the force pushing cart to the ground at 
        // high speed thus making it more stable. 0 means no force (useful if 
        // your vehicle is intended to takeoff at high speed).
        [KSPField]
        public float compensationLowGee = 1F;
        [KSPField]
        public float compensationSpeed = 0.2F;

        // Resource Usage
        [KSPField]
        public string fuelName = "";
        [KSPField]
        public float idleFuelDrainPerSecond = 0F;
        [KSPField]
        public float lightsFuelDrainPerSecond = 0F;
        [KSPField]
        public float fuelDrainMeterSecondMultiplier = 0F;
        [KSPField]
        public float brakeRegenMeterSecondMultiplier = 0F;

        // Lights:
        // Intensity is ( 0..8 ), range in meters, angle in degrees.
        [KSPField]
        public float lightIntensity = 0.5F;
        [KSPField]
        public float headLightIntensity = 1;
        [KSPField]
        public float lightRange = 10;
        [KSPField]
        public float headLightRange = 1000;
        [KSPField]
        public float lightSpotAngle = 120;
        [KSPField]
        public float headLightSpotAngle = 70;

        // Light colors (Red, Green and Blue, range 0..1): 
        [KSPField]
        public Vector3 headLightColor = CFGColor.Vector3(Color.white);
        [KSPField]
        public Vector3 stopLightColor = CFGColor.Vector3(Color.red);
        [KSPField]
        public Vector3 parkingLightColor = CFGColor.Vector3(Color.yellow);

        // Key and axis bindings:
        // A key code to which a specific action is bound (like "k" or "B" or 
        // "up" or "[6]" for Numpad6). For a list of available key codes see
        // http://unity3d.com/support/documentation/Manual/Input.html
        [KSPField]
        public string keyToggleLights = "u";
        [KSPField]
        public string keyBrake = "n";
        [KSPField]
        public string keyHandBrake = "b";
        [KSPField]
        public string keyCruiseControl = "o";
        // A key to deploy/retract wheels (if "deployable = true" in "Engine" 
        // section) is the same as landing gear key in KSP game settings.

        // Valid values: an empty string (no control), "X", "Y", "Z", "Pitch", 
        // "Yaw", "Roll", "Throttle" (not recommended as the reverse gear is 
        // not implemented yet), and optionally "Reversed". For example,
        //    Pitch
        // or
        //    Z Reversed
        [KSPField]
        public string axisMotor = "Y reversed";
        [KSPField]
        public string axisSteer = "X reversed";
        [KSPField]
        public string replacementWheelShader = "";

        // --------- Inter-Plugin Interface --------- 
        // Use System.Type.GetProperty to access

        public bool hasWheels { get { return mWheels.Count > 0; } }
        public bool hasLights { get { return mLights.Count > 0; } }
        public bool hasMotor { get { return mWheels.hasMotor && ((forwardTorque > 0) || (backwardTorque > 0)); } }
        public bool hasBrake { get { return hasWheels && (brakeTorque > 0); } }
        public bool hasHandbrake { get { return hasBrake && (keyHandBrake != ""); } }
        public bool hasCruiseControl { get { return hasMotor && (keyCruiseControl != ""); } }
        public bool canSteer { get { return mWheels.canSteer && (maxSteerAngle > 0); } }
        public bool canDeploy { get { return hasWheels && deployable; } }

        public bool wheelsCreated { get { return mWheels.wheelsCreated; } } // false if not deployed, or packed for orbit
        public float speed { get { return mSpeed; } } // +/- meters per second
        public Vector3 direction { get { return transform.TransformDirection(mWheels.direction); } } //...to which cart rides. In World Space.
        public bool controllable { get { return mVesselState.controllable; } } // not the same as Part.isControllable! 
        public bool onRemoteControl { get { return mVesselState.onRemoteControl; } }

        public bool wheelsDeployed
        {
            get { return mWheels.deployed; }
            set
            {
                if (!canDeploy)
                {
                    value = true;
                }
                if (value != mWheels.deployed)
                {
                    mWheels.deployed = value;
                    if (value)
                    {
                        mFirstTimeDeploying = true;
                    }
                    else
                    {
                        cruiseControl = false;
                        brake = false;
                        handbrake = false;
                    }
                }
            }
        }

        public bool motorEnabled
        {
            get { return mMotorEnabled; }
            set
            {
                if (!hasMotor)
                {
                    value = false;
                }
                if (value != mMotorEnabled)
                {
                    mMotorEnabled = value;
                    if (!value)
                    {
                        cruiseControl = false;
                    }
                }
            }
        }

        public bool steeringEnabled
        {
            get { return mSteeringEnabled; }
            set
            {
                if (!canSteer)
                {
                    value = false;
                }
                if (value != mSteeringEnabled)
                {
                    mSteeringEnabled = value;
                }
            }
        }

        public bool lights
        {
            get { return mLightsOn; }
            set
            {
                if (!hasLights)
                {
                    value = false;
                }

                if (value != mLightsOn)
                {
                    mLightsOn = value;
                }
            }
        }

        public bool brake
        {
            get { return mBrake; }
            set
            {
                if (!hasBrake)
                {
                    value = false;
                }
                if (value != mBrake)
                {
                    mBrake = value;
                    if (value)
                    {
                        handbrake = false;
                    }
                }
            }
        }

        public bool handbrake
        {
            get { return mHandbrake; }
            set
            {
                if ((!hasHandbrake) || (!wheelsDeployed))
                {
                    value = false;
                }

                if (value != mHandbrake)
                {
                    mHandbrake = value;
                    if (value)
                    {
                        cruiseControl = false;
                        brake = false;
                    }
                }
            }
        }

        public bool cruiseControl
        {
            get { return mCruiseControl; }
            set
            {
                if ((!hasCruiseControl) || (!wheelsDeployed) || (!motorEnabled))
                {
                    value = false;
                }
                if (value != mCruiseControl)
                {
                    mCruiseControl = value;
                    if (value)
                    {
                        cruiseSpeed = speed;
                        if (mCruiseControl)
                        {
                            handbrake = false;
                        }
                    }
                }
            }
        }

        public float cruiseSpeed
        { // +/- meters per second
            get { return mCruiseSpeed; }
            set
            {
                if (cruiseControl && (value != mCruiseSpeed))
                {
                    mCruiseSpeed = 0.5F * (int)(value * 2);
                }

                if (mCruiseSpeed == 0)
                {
                    cruiseControl = false;
                }
            }
        }

        public float throttle
        { // -1..+1
            get { return mThrottle; }
            set
            {
                if ((!wheelsCreated) || (!motorEnabled))
                {
                    value = 0;
                }
                if (mThrottle.currentValue != value)
                {
                    mThrottle.value = value;
                    if (value != 0)
                    {
                        mHandbrake = false;
                    }
                }
            }
        }

        public float steering
        { // -1..+1
            get { return mSteering; }
            set
            {
                if ((!wheelsCreated) || (!steeringEnabled))
                {
                    value = 0;
                }
                if (mSteering.currentValue != value)
                {
                    mSteering.value = value;
                }
            }
        }

        // --------- Internals --------- 

        public string menuHint { get { return mVesselState.menuHint; } }

        [KSPField]
        private bool mMotorEnabled = true;
        [KSPField]
        private bool mSteeringEnabled = true;
        [KSPField]
        private bool mLightsOn = false;
        [KSPField]
        private bool mBrake = false;
        [KSPField]
        private bool mHandbrake = false;
        [KSPField]
        private bool mCruiseControl = false;
        [KSPField]
        private bool mFirstTimeDeploying = true;

        private float mCruiseSpeed = 0;
        private float mSpeed = 0;
        private bool mMapMode = false;
        private bool mStateLoaded = false;

        // Resource management
        private float lastResourceDrain = 0;

        private List<CartLight> mLights = new List<CartLight>();
        private List<CartLight> mStopLights = new List<CartLight>();
        private CartWheelList mWheels = new CartWheelList();

        private Callback mapCallback = null;

        // FlyByWire refresh
        private FlightInputCallback flyCallback = null;
        private int partCount;
        private Vessel lastVessel;

        private const float geeForceAtKerbin = 9.81F;

        private VInfoBox indicator = null,
                         handbrakeIndicator = null,
                         cruiseIndicator = null;

        private CartVesselState mVesselState = new CartVesselState();
        private CartControlQueue mControlQueue = new CartControlQueue();

        private SmoothControl mThrottle = new SmoothControl(2);
        private SmoothControl mSteering = new SmoothControl(5);
        private PidController mCruiseController = new PidController(
                                  10, 1e-5F, 1e-5F, 50, 1);
        // Use calibratePID method if you're 
        // unhappy with these values :-P   

        // --------- Context Menu -------------------
        [KSPField(guiName = "MenuHint", guiActive = false, category = "menu", isPersistant = false)]
        public string MenuHint = "menu disabled";

        [KSPEvent(name = "ToggleSteeringEnabled", active = true, guiActive = true, guiName = "Enable Steering", category = "engine")]
        public void ToggleSteeringEnabled()
        {
            steeringEnabled = !steeringEnabled;
            UpdateMenu(true);
        }

        [KSPEvent(name = "ToggleMotorEnabled", active = true, guiActive = true, guiName = "Enable Motor", category = "engine")]
        public void ToggleMotorEnabled()
        {
            motorEnabled = !motorEnabled;
            UpdateMenu(true);
        }

        [KSPEvent(name = "ToggleHandbrake", active = true, guiActive = true, guiName = "Turn Handbrake On", category = "engine")]
        public void ToggleHandbrake()
        {
            handbrake = !handbrake;
            UpdateMenu(true);
        }

        [KSPEvent(name = "ToggleCruiseControl", active = true, guiActive = true, guiName = "Cruise Control", category = "engine")]
        public void ToggleCruiseControl()
        {
            cruiseControl = !cruiseControl;
            UpdateMenu(true);
        }

        [KSPEvent(name = "ToggleLights", active = true, guiActive = true, guiName = "Turn Lights On", category = "lights")]
        public void ToggleLights()
        {
            lights = !lights;
            UpdateMenu(true);
        }

        [KSPEvent(name = "ToggleWheelsDeployed", active = true, guiActive = true, guiName = "Deploy Wheels", category = "engine")]
        public void ToggleWheelsDeployed()
        {
            wheelsDeployed = !wheelsDeployed;
            UpdateMenu(true);
        }

        private int frameCnt = 0;

        private void updateItem(string name, string caption, bool active, ref bool changed)
        {
            if ((caption != Events[name].guiName) || (active != Events[name].active))
            {
                changed = true;
            }

            Events[name].active = active;
            Events[name].guiName = caption;
        }

        public void UpdateMenu(bool updateCounterparts)
        {
            bool hasCart = true;
            bool hasVessel = hasCart && vessel && vessel.isActiveVessel;
            bool hasControls = hasVessel && (menuHint == "");
            bool hasHint = hasVessel && (menuHint != "");
            bool changed = false;

            updateItem("ToggleSteeringEnabled",
                        steeringEnabled ? "Disable Steering" : "Enable Steering",
                        hasControls && canSteer, ref changed);

            updateItem("ToggleMotorEnabled",
                        motorEnabled ? "Disable Motor" : "Enable Motor",
                        hasControls && hasMotor, ref changed);

            updateItem("ToggleHandbrake",
                        handbrake ? "Turn Handbrake Off" : "Turn Handbrake On",
                        hasControls && hasHandbrake, ref changed);

            updateItem("ToggleCruiseControl",
                        cruiseControl ?
                           String.Format("Keeping {0:0.0} m/s...", cruiseSpeed) :
                           "Cruise Control",
                        hasControls && hasCruiseControl, ref changed);

            updateItem("ToggleLights",
                        lights ? "Turn Lights Off" : "Turn Lights On",
                        hasControls && hasLights, ref changed);

            updateItem("ToggleWheelsDeployed",
                        wheelsDeployed ? "Retract Wheels" : "Deploy Wheels",
                        hasControls && canDeploy, ref changed);

            if ((!hasCart) || !(canSteer || hasMotor || hasHandbrake || hasCruiseControl || hasLights || canDeploy)) hasHint = false;

            Fields["MenuHint"].guiName = hasHint ? menuHint : "";
            Fields["MenuHint"].guiActive = hasHint && (menuHint != "");

            if (hasCart && updateCounterparts)
            {
                foreach (Part p in part.symmetryCounterparts)
                {
                    foreach (PartModule m in p.Modules)
                    {
                        if (m is ModuleCleverWheeledVehicle)
                        {
                            (m as ModuleCleverWheeledVehicle).steeringEnabled = steeringEnabled;
                            (m as ModuleCleverWheeledVehicle).motorEnabled = motorEnabled;
                            (m as ModuleCleverWheeledVehicle).handbrake = handbrake;
                            (m as ModuleCleverWheeledVehicle).cruiseControl = cruiseControl;
                            (m as ModuleCleverWheeledVehicle).lights = lights;
                            (m as ModuleCleverWheeledVehicle).wheelsDeployed = wheelsDeployed;
                            break;
                        }
                    }
                }
            }


            // If something changed, hide context menu for next 1 frame 
            // to give buttons a chance to update their labels (a 
            // workaround necessary in KSP v0.16).
            if (changed)
            {
                foreach (BaseEvent e in Events)
                {
                    e.guiActive = false;
                }

                frameCnt = 2;
            }
        }

        // --------- Implementation --------- 

        // Creates a new wheel if all the necessary objects can be found.
        private void addWheel(Transform p)
        {
            // Get wheel ID and type.
            string[] s = p.name.Substring(5).Split('_');
            string n = s[0], t = s.Length <= 1 ? "" : s[1];

            // Find corresponding anchor and suspension lever.
            Transform a = p.parent.Find("anchor" + n),
                      l = p.parent.Find("lever" + n);
            if (!l)
            {
                l = p.parent.Find("rotatingLever" + n);
            }

            if (!a)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": ERROR - no anchor found for " + p.name);
                return;
            }

            // Parse wheel type.
            bool motor = false,
                 steering = false;

            if (t.Equals("ms"))
            {
                motor = true;
                steering = true;
            }
            else if (t.Equals("m"))
            {
                motor = true;
            }
            else if (t.Equals("s"))
            {
                steering = true;
            }
            else if (!t.Equals(string.Empty))
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": ERROR - unknown wheel type " + p.name + ". Use \"m\", \"s\", or \"ms\".");
                return;
            }

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Found " + p.name + " with " + a.name + (l ? ", " + l.name : "") + (motor ? ", motor" : "") + (steering ? ", steering" : ""));

            // Create a wheel.
            CartWheel w = new CartWheel(mWheels, p, a, l, motor, steering);
            w.mass = wheelMass;
            w.spring = suspensionSpring;
            w.damper = suspensionDamper;
            mWheels.Add(w);

            if (replacementWheelShader != "")
            {
                Shader r = Shader.Find(replacementWheelShader);
                if (r && p.renderer)
                {
                    p.renderer.material.shader = r;
                }
            }
        }

        // Creates a new light.
        private void addLight(Transform p)
        {
            // Parse light type.  
            string s = p.name.Substring(6);
            bool head = false,
                 stop = false;

            if (s.StartsWith("head"))
            {
                head = true;
            }
            else if (s.StartsWith("stop"))
            {
                stop = true;
            }
            else if (!s.StartsWith("parking"))
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Error - unknown light " + p.name);
                return;
            }

            if (p.GetComponent<Light>() != null)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Error - light already added to " + p.name);
                return;
            }

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Found " + p.name);

            // Create a light.
            CartLight l = new CartLight(p);
            l.range = head ? headLightRange : lightRange;
            l.spotAngle = head ? headLightSpotAngle : lightSpotAngle;
            l.intensity = head ? headLightIntensity : lightIntensity;
            l.color = CFGColor.Color(head ? headLightColor : stop ? stopLightColor : parkingLightColor);

            if (stop)
            {
                mStopLights.Add(l);
            }
            else
            {
                mLights.Add(l);
            }
        }

        private void updateMenu()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "updateMenu Start");
            UpdateMenu(false);
        }

        private static Shader glassShader = Shader.Find("Transparent/Specular"),
                              metalShader = Shader.Find("KSP/Specular");

        // Find and initialize all the wheels, lights and metal/glass parts.
        public override void OnStart(StartState state)
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "OnStart Start");

            // Check if vessel and part != null for avoiding to execute code in VAB/SPH
            if (vessel != null && part != null)
            {
                // Sets up callbacks into MapView to dim the lights when Map is 
                // invoked (otherwise headlight glare will be visible on planet's 
                // surface).    
                // Also sets a FlyByWire callback 'cause onCtrlUpd just does not 
                // receive controls in case of a craft with crew, but without command 
                // pod. 
                // Set mapView callbacks.
                if (mapCallback == null)
                {
                    Logger.Debug(debug, "ModuleCleverWheeledVehicle", "mapCallback");
                    mapCallback = new Callback(onMapView);
                    MapView.OnEnterMapView += mapCallback;
                    MapView.OnExitMapView += mapCallback;
                    part.OnJustAboutToBeDestroyed += CleanUpMapCallback;
                }

                if (flyCallback == null && vessel.OnFlyByWire != null)
                {
                    Logger.Debug(debug, "ModuleCleverWheeledVehicle", "flyCallback");
                    flyCallback = new FlightInputCallback(flyByWire);
                    vessel.OnFlyByWire += flyCallback;
                    part.OnJustAboutToBeDestroyed += CleanUpFlyCallback;
                }

                // Store vessel and part count
                lastVessel = vessel;
                partCount = vessel.Parts.Count;
                Logger.Debug(debug, "ModuleCleverLegacyCart", "partCount:[" + partCount.ToString() + "]");
            }

            // Initialize wheels and lights.       
            foreach (Transform t in transform.Find("model"))
            {
                if (t.name.StartsWith("glass"))
                {
                    t.renderer.material.shader = glassShader;
                }
                else if (t.name.StartsWith("metal"))
                {
                    t.renderer.material.shader = metalShader;
                }
                else if (t.name.StartsWith("wheel"))
                {
                    addWheel(t);
                }
                else if (t.name.StartsWith("light_"))
                {
                    addLight(t);
                }
            }

            if (deployTime <= 0.1F)
            {
                deployTime = 0.1F;
            }

            mWheels.steerVisibility = steerVisibility;
            mWheels.deployTime = deployTime;
            mWheels.forwardFriction = forwardFriction;
            mWheels.sidewaysFriction = sidewaysFriction;

            if (!mStateLoaded)
            {
                wheelsDeployed = !deployable;
            }

            mWheels.Initialize();

            if ((!deployable) && !wheelsDeployed)
            {
                wheelsDeployed = true;
            }

            updateLights();
            updateIndicator();

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "OnStart End");
        }

        // Removes MapView and FlyByWire callbacks when the part is destroyed.
        public void CleanUpMapCallback()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CleanUpMapCallback Start");

            if (mapCallback != null)
            {
                MapView.OnEnterMapView -= mapCallback;
                MapView.OnExitMapView -= mapCallback;
            }
        }

        // Removes MapView and FlyByWire callbacks when the part is destroyed.
        public void CleanUpFlyCallback()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CleanUpFlyCallback Start");

            if (flyCallback != null)
            {
                vessel.OnFlyByWire -= flyCallback;
            }
        }

        // Dims lights when switching to Map view to eliminate headlight 
        // glares on the planet's surface.
        // TODO: When orbiting at x5 warp glares are displayed even in 
        // Orbit mode. Do something about it.
        private void onMapView()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "onMapView MapView.MapIsEnabled:[" + MapView.MapIsEnabled + "] mMapMode:[" + mMapMode + "]");

            if (MapView.MapIsEnabled && !mMapMode)
            {
                if (hasLights && lights)
                {
                    Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Entering map, dimming lights");
                    mMapMode = true;
                    updateLights();
                }
            }
            else if (mMapMode && !MapView.MapIsEnabled)
            {
                if (hasLights && lights)
                {
                    Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Exiting map, shining lights");
                    mMapMode = false;
                    updateLights();
                }
            }

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "onMapView End");
        }

        // Switches lights off and on according to situation. See also 
        // onPartUpdate method.
        private void updateLights()
        {
            foreach (CartLight l in mLights)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "updateLights mLights:[" + l.enabled + "]");
                l.enabled = lights && !mMapMode;
            }

            foreach (CartLight l in mStopLights)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "updateLights mStopLights:[" + l.enabled + "]");
                l.enabled = ((throttle < -0.2F) || brake || handbrake) && !mMapMode;
            }

        }

        // Switches handbrake/cruise control indicators off and on according to 
        // situation. See also onPartUpdate method.
        private void updateIndicator()
        {
            if ((handbrake || cruiseControl) && (indicator == null))
            {
                indicator = part.stackIcon.DisplayInfo();
            }
            else if ((indicator != null) && !(handbrake || cruiseControl))
            {
                part.stackIcon.ClearInfoBoxes();
                indicator = null;
                handbrakeIndicator = null;
                cruiseIndicator = null;
            }

            if (handbrake && (handbrakeIndicator == null) && (indicator != null))
            {
                cruiseIndicator = null;
                handbrakeIndicator = indicator;
                handbrakeIndicator.SetMsgBgColor(XKCDColors.SlateGreen);
                handbrakeIndicator.SetMsgTextColor(XKCDColors.AcidGreen);
                handbrakeIndicator.SetMessage("Handbrake");
                handbrakeIndicator.SetProgressBarBgColor(XKCDColors.SlateGreen);
                handbrakeIndicator.SetValue(0);
            }

            if (cruiseControl && (cruiseIndicator == null) && (indicator != null))
            {
                handbrakeIndicator = null;
                cruiseIndicator = indicator;
                cruiseIndicator.SetMsgBgColor(XKCDColors.DarkCyan);
                cruiseIndicator.SetMsgTextColor(XKCDColors.LightCyan);
                cruiseIndicator.SetMessage("Cruise");
                cruiseIndicator.SetProgressBarBgColor(XKCDColors.DarkCyan);
                cruiseIndicator.SetProgressBarColor(XKCDColors.LightCyan);
            }

            if (cruiseIndicator != null)
            {
                cruiseIndicator.SetValue(0.5F + throttle / 2);
            }

            part.stackIcon.SetIconColor(wheelsCreated ? XKCDColors.White : XKCDColors.SlateGrey);
        }

        // Destroys all the physics objects when the part is packed to orbit.
        void onPack()
        {
            if (wheelsCreated)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "CART " + this.name + ": Destroying wheels");
                mWheels.DestroyWheels();
            }
        }

        // Process wheels physics and animation.
        // Currently all the physics objects are created in this call, as soon 
        // as the part has created its rigidbody. Physics creation in OnPartStart 
        // or OnFlightStart does not work because the part might not have 
        // a rigidbody at that moment.
        void FixedUpdate()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "FixedUpdate Start");

            // Update resource usage will force engine out and steering out
            UpdateResourceDrain();

            if ((rigidbody != null) && (!rigidbody.isKinematic) && (!wheelsCreated) && wheelsDeployed)
            {
                Logger.Debug(debug, "CART " + name + ": Creating wheels " + (mFirstTimeDeploying ? "first time" : ""));
                mWheels.CreateWheels(mFirstTimeDeploying);
            }

            if (!wheelsCreated)
            {
                return;
            }

            // Refresh flybywire if needed
            if (vessel.Parts.Count != partCount)
            {
                Logger.Debug(debug, "ModuleCleverWheeledVehicle", "re-subscribe flyCallback");
                lastVessel.OnFlyByWire -= flyCallback;

                flyCallback = new FlightInputCallback(flyByWire);
                vessel.OnFlyByWire += flyCallback;
                part.OnJustAboutToBeDestroyed += CleanUpFlyCallback;

                partCount = vessel.Parts.Count;
                lastVessel = vessel;
            }

            // we've got wheels. Next time when 
            // we'll need to _create_ wheels (_not_ "deploy"!) 
            // we can start with wheels' original positions; no 
            // need to retract them.
            mFirstTimeDeploying = false;

            mSpeed = Vector3.Dot(rigidbody.velocity, direction);
            float vel = Mathf.Abs(mSpeed);

            // Steer angle amplitude. Constant at low speed, low at high speed.
            float steer = vel;

            if (steerLimitSpeed > 0)
            {
                steer /= steerLimitSpeed;
            }

            if (steer < 1)
            {
                steer = 1;
            }
            else
            {
                steer = 1 / (steerSpeedFalloff * steer - steerSpeedFalloff + 1);
            }

            steer = maxSteerAngle * steer * steering;

            // Wheels friction is limiting cart's top speed and coasting distance.
            float friction = frictionTorque * (steerLimitSpeed + vel);
            if (friction < 0.001F)
            {
                friction = 0; // it jerks otherwise.
            }

            // Wheels torque. Use cruise control, if no manual controls are applied.
            if ((mThrottle.currentValue == 0) && cruiseControl)
            {
                mThrottle.currentValue = mCruiseController.Control(cruiseSpeed - speed);
            }

            float motor = throttle > 0 ? (throttle * forwardTorque) : (throttle * backwardTorque);
            float braking = ((brake || handbrake) ? brakeTorque : 0) + friction;

            // Now animate all the wheels.
            bool grounded;
            mWheels.FixedUpdate(motorEnabled ? motor : 0, braking, steeringEnabled ? steer : 0, out grounded);

            // Then apply the force pushing cart to the ground -- high at the Mun, 
            // low at Kerbin, and proportional to speed.
            if (grounded && (vessel.situation == Vessel.Situations.LANDED || vessel.situation == Vessel.Situations.PRELAUNCH))
            {
                Vector3 gee = FlightGlobals.getGeeForceAtPosition(transform.position);
                gee = transform.InverseTransformDirection(gee);

                Vector3 v = gee.normalized * compensationLowGee *
                            (geeForceAtKerbin - gee.magnitude) * mVesselState.mass -
                Vector3.up * compensationSpeed * vel;

                // push the cart to the ground.
                rigidbody.AddRelativeForce(v);
            }

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "OnFixedUpdate End");
        }

        private CartControlState GetControls()
        {
            CartControlState ret;
            ret.toggleLights = (keyToggleLights != "") && Input.GetKeyDown(keyToggleLights.ToLower());
            ret.toggleHandbrake = (keyHandBrake != "") && Input.GetKeyDown(keyHandBrake.ToLower());
            ret.toggleCruise = (keyCruiseControl != "") && Input.GetKeyDown(keyCruiseControl.ToLower());
            ret.toggleWheels = GameSettings.LANDING_GEAR.GetKeyDown();
            ret.brake = (keyBrake != "") && Input.GetKey(keyBrake.ToLower());
            ret.time = Planetarium.GetUniversalTime();
            return ret;
        }

        // Processes keyboard input.
        // NOTE. Doing that in Part.OnCtrlUpd does not work 'cause it randomly 
        // skips some of the keystrokes. If you're processing only button 
        // _holds_ (i.e. using Input.GetKey only) then OnCtrlUpdate would be a 
        // better choice 'cause it's called for an active vessel only (while
        // onPartUpdate is called for everything in the game).
        void Update()
        {
            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "Update Start");

            mVesselState.Update(this.part);

            // Receive controls for an active vessel only.
            if (mVesselState.controllable)
            {
                CartControlList lst = new CartControlList(GetControls());
                if (mVesselState.onRemoteControl)
                {
                    lst = mControlQueue.Delay(lst, mVesselState.remoteControlDelay);
                }
                else
                {
                    mControlQueue.Clear();
                }

                foreach (CartControlState s in lst)
                {
                    if (s.toggleLights)
                    {
                        lights = !lights;
                    }

                    if (s.toggleHandbrake)
                    {
                        handbrake = !handbrake;
                    }

                    brake = s.brake;

                    if (s.toggleWheels)
                    {
                        wheelsDeployed = !wheelsDeployed;
                    }

                    if (s.toggleCruise)
                    {
                        cruiseControl = !cruiseControl;
                    }
                }
            }

            // calibratePID(); 
            if (part.isControllable && vessel == FlightGlobals.ActiveVessel && InputLockManager.IsUnlocked(ControlTypes.THROTTLE))
            {
                updateMenu();
                updateLights();
                updateIndicator();

                // Context Menu
                if (frameCnt > 0)
                {
                    frameCnt--;
                    if (frameCnt == 0)
                    {
                        foreach (BaseEvent e in Events)
                        {
                            e.guiActive = e.active;
                        }
                    }
                }
            }

            Logger.Debug(debug, "ModuleCleverWheeledVehicle", "OnUpdate End");
        }

        private float AxisValue(FlightCtrlState s, string whichOne)
        {
            string[] str = whichOne.ToLower().Split(' ');
            float ret = 0;

            switch (str[0])
            {
                case "x": ret = s.X; break;
                case "y": ret = s.Y; break;
                case "z": ret = s.Z; break;
                case "pitch": ret = s.pitch; break;
                case "yaw": ret = s.yaw; break;
                case "roll": ret = s.roll; break;
                case "throttle": ret = s.mainThrottle; break;
            }

            return ((str.Length >= 2) && (str[1] == "reversed")) ? -ret : ret;
        }

        // Movement and steering are controlled by KSP's axes
        // FIXME: In case of some axis set to Throttle, Handbrake toggle should 
        // drop the throttle to zero. Moreover, a backward gear should be added 
        // somehow.
        // onCtrlUpd does not receive controls in case of a craft with crew, but 
        // without command pod. We need to use "fly-by-wire" here.
        // Damn KSP :((((
        private void flyByWire(FlightCtrlState s)
        {
            if (mVesselState.controllable)
            {
                steering = AxisValue(s, axisSteer);
                throttle = AxisValue(s, axisMotor);
            }
            else
            {
                steering = 0;
                throttle = 0;
            }
        }

        /// <summary>
        /// Called when PartModule is asked to save its values.
        /// Can save additional data here.
        /// </summary>
        /// <param name='node'>The node to save in to</param>
        public override void OnSave(ConfigNode node)
        {
        }

        /// <summary>
        /// Called when PartModule is asked to load its values.
        /// Can load additional data here.
        /// </summary>
        /// <param name='node'>The node to load from</param>
        public override void OnLoad(ConfigNode node)
        {
            updateLights();
            updateIndicator();
            mStateLoaded = true;
        }

        private void UpdateResourceDrain()
        {
            Logger.Debug(debug, "UpdateResourceDrain()");

            if (fuelName != "" && part.isControllable && vessel == FlightGlobals.ActiveVessel)
            {
                if (Time.time - lastResourceDrain > 1f)
                {
                    double availableResource = ResourceUtils.GetAvailableResource(vessel, fuelName);
                    if ((speed > 0.1 || speed < -0.1) && (brake || handbrake))
                    {
                        double resourceGain = brakeRegenMeterSecondMultiplier * speed * -1;
                        if (resourceGain > 0) resourceGain = resourceGain * -1;
                        Logger.Debug(debug, "UpdateResourceDrain() resourceGain", resourceGain.ToString());
                        ResourceUtils.GetSourceResourceContainer(vessel, fuelName).RequestResource(fuelName, resourceGain);
                        availableResource = ResourceUtils.GetAvailableResource(vessel, fuelName);
                    }
                    if (availableResource > 0)
                    {
                        float resourceUsage = 0;
                        if (lights)
                        {
                            resourceUsage += lightsFuelDrainPerSecond;
                        }
                        if ((throttle == 1 || throttle == -1 || cruiseControl) && (speed > 0.1 || speed < -0.1))
                        {
                            if (speed > 0.1)
                            {
                                resourceUsage += fuelDrainMeterSecondMultiplier * speed;
                            }
                            else if (speed < -0.1)
                            {
                                resourceUsage += fuelDrainMeterSecondMultiplier * speed * -1;
                            }
                        }
                        else
                        {
                            resourceUsage += idleFuelDrainPerSecond;
                        }

                        if (availableResource >= resourceUsage)
                        {
                            float res = ResourceUtils.GetSourceResourceContainer(vessel, fuelName).RequestResource(fuelName, resourceUsage);
                            if (res == 0)
                            {
                                motorEnabled = false;
                                steeringEnabled = false;
                                lights = false;
                            }
                        }
                        else
                        {
                            motorEnabled = false;
                            steeringEnabled = false;
                            lights = false;
                        }
                    }
                    else
                    {
                        motorEnabled = false;
                        steeringEnabled = false;
                        lights = false;
                    }

                    lastResourceDrain = Time.time;
                }
            }
        }

        /* PID Calibration  Code. Leave it here, you may need it.

            private float step( float value, bool up ) {
                float order = Mathf.Log10( value );
                if( order < 0 )
                    order --;
                order = ( int ) order;
                float exp = Mathf.Pow( 10, order );  
                float num = Mathf.Round( value / exp ); 
                if( up ) {
                    if( num >= 10 )
                        num += 10;
                    else
                        num ++;
                } else {
                    if( num <= 1 )
                        num -= 0.1F;
                    else
                        num --;
                }
                return num * exp;
            }

            private void calibratePID() {
                if( Input.GetKeyDown( "[7]" ) ) {
                    cruiseController.mKp = step( cruiseController.mKp, false );
                    Debug.Log( name + " Kp: " + cruiseController.mKp );
                }
                if( Input.GetKeyDown( "[9]" ) ) {
                    cruiseController.mKp = step( cruiseController.mKp, true );
                    Debug.Log( name + " Kp: " + cruiseController.mKp );
                }

                if( Input.GetKeyDown( "[4]" ) ) {
                    cruiseController.mKi = step( cruiseController.mKi, false );
                    Debug.Log( name + " Ki: " + cruiseController.mKi );
                }
                if( Input.GetKeyDown( "[6]" ) ) {
                    cruiseController.mKi = step( cruiseController.mKi, true );
                    Debug.Log( name + " Ki: " + cruiseController.mKi );
                }

                if( Input.GetKeyDown( "[1]" ) ) {
                    cruiseController.mKd = step( cruiseController.mKd, false );
                    Debug.Log( name + " Kd: " + cruiseController.mKd );
                }
                if( Input.GetKeyDown( "[3]" ) ) {
                    cruiseController.mKd = step( cruiseController.mKd, true );
                    Debug.Log( name + " Kd: " + cruiseController.mKd );
                }
            }
        */
    }
}
